AWS Lambdaを使ってEC2停止忘れを通知してみた
こんにちは。 ネクストモード株式会社 の田口です。
AWSの習熟でリソースを作成したものの、停止や削除を忘れてしまう経験をした人は多いのではないでしょうか。コストが発生することを知らなかったり、オートスケールの設定が残っていて消したつもりが別のリソースが起動したことに気づかなかったりと、理由は様々あると思います。
今回は、常時起動が必要なEC2インスタンスが存在するAWSアカウントにおいて、 習熟用に作成したEC2インスタンスの停止忘れをお知らせしてくれる仕組みを考えてみました。(もちろん強制停止させることも可能ですが、あくまでリソースの作成者が自ら停止する習慣をつけてほしいので、お知らせするだけにしました)
機能要件
EC2の使用量を削減する解決策として、AWS公式ページではLambdaを使用して特定の時間自動停止および起動する仕組みが紹介されています。この仕組みは対象リソースが決まっている場合に有効ですが、習熟で一時的に作成するリソースがある場合には向いていません。また、タグで自動停止の対象を指定するやり方もありますが、習熟用のリソースに忘れずタグをつけることは現実的に難しいのではないでしょうか。
そこで今回実装する仕組みに求める要件を以下としました。
- 習熟用のリソースにはタグをつけなくても停止忘れに気づける
- 強制停止ではなく、注意喚起にとどめる
- 常時起動しておくべきものは通知の対象から除外する
前提条件
機能要件も踏まえ、以下を前提条件とします。
- 常時起動しておく必要があるEC2インスタンスが存在する
- 常時起動しておくEC2インスタンスにのみ、特定のタグが設定されている
習熟用のリソースとは逆に、常時起動が必要なEC2インスタンス側には特定のタグをつけていることはルール化すれば対応できると思います。当然タグをつけ忘れると停止忘れの通知対象となりますが、それも含めて注意喚起ができれば問題ありません。例として常時起動対象には以下のタグを付与しています。
key | 値 |
---|---|
status | running |
構成図
構成は非常にシンプルです。今回通知はEメールにしていますが、slackに投稿するのも便利です。 停止忘れをチェックするタイミングですが、長期間起動し続けなければコストインパクトは大きくないので、頻度は控えめでもかまいません。 私の環境では、毎日0時でセットしました。
サービス | 機能 |
---|---|
Amazon EventBridge (cloudwatch events) | 停止忘れをチェックする頻度を決めてLambdaを呼び出します。 |
AWS Lambda | AWSアカウント内のEC2インスタンスの起動状態を取得し、条件に合致するインスタンスIDをリストにしてSNSに渡します。 Lambdaの関数名を「ec2_running_check」としましたが、名前はお好みでかまいません。 |
Amazon Simple Notification Service(SNS) | 登録したアドレスにEメールを送付します。 |
設定手順
手順としては大きく分けると下記の4つになります。
- IAM Roleの作成
- SNSの作成
- Lambda Functionの作成
- Amazon EventBridge(CloudWatch Events)の作成
IAM Roleの作成
Lambdaが実行できるようにIAM Roleを割り当てます。以下のように設定しました。
- ロール名:ec2_running_check_role を入力
- ポリシー:
AWSLambdaBasicExecutionRole
,AmazonEC2ReadOnlyAccess
AWSLambdaSNSPublishPolicyExecutionRole
をアタッチ
SNSの作成
SNSのトピックを作成し、通知したいEメールアドレスのサブスクリプションを作成します。
- タイプ:スタンダード を選択
- 名前:ec2_running_check を入力
- 表示名:ec2_running_check を入力
- トピックARN:ec2_running_checkのARN を選択
- プロトコル:Eメール を選択
- エンドポイント:通知したいメールアドレス を入力
作成したトピックARNの情報をメモしておきます。
Lambda Functionの作成
LambdaではステータスがrunningであるEC2インスタンスIDの集合➀と常時起動のタグがあるEC2インスタンスIDの集合➁の差集合 を算出してみました。下記図の緑部分が停止忘れの可能性がある対象です。
AWS Lambdaで 関数 > 一から作成 を選択します。
- 関数名:ec2_running_check を入力
- ランタイム:python3.8 を選択
- デフォルトの実行ロールを選択:既存のロールを使用する を選択
- 既存のロール:作成した ec2_running_check_role を選択
- 関数の作成 に進む(VPCの指定は不要です)
コードは下記の内容を設定します。
12行目のstatus
とrunning
には、常時起動用で定義したタグの設定にあわせてください。
また、31行目のTopicArn
の内容は、ご自身の環境で作成したArnの情報に置き換えてください。
def lambda_handler(event,context): # running状態のインスタンスのインスタンスIDを取得し集合にする :➀ no_tag = ec2.describe_instances( Filters=[{'Name':'instance-state-name','Values':['running']}] ) no_tag_set = set(ec2['InstanceId'] for resId in no_tag['Reservations'] for ec2 in resId['Instances']) # 常時起動のタグがついたインスタンスIDを取得し集合にする:➁ with_tag = ec2.describe_instances( Filters=[{'Name':'tag:status','Values': ['running']}] ) with_tag_set = set(ec2['InstanceId'] for resId in with_tag['Reservations'] for ec2 in resId['Instances']) # running状態のインスタンスと常時起動タグつきインスタンスとの差集合演算を行う:➀ - ➁ set_target_instances = no_tag_set - with_tag_set # 演算結果をリストにする list_target_instances = list(set_target_instances) # 停止忘れ or 常時起動のタグつけ忘れがある場合はSNSで通知 if list_target_instances == []: # 停止忘れがない場合は何もしない pass else: request = { 'TopicArn':"arn:aws:sns:ap-northeast-1:<account_id>:ec2_running_check", 'Message':"停止忘れかもしれません。確認しましょう。 \n\n" + ('\n'.join(list_target_instances)), 'Subject':"ec2_running_check" } sns_client.publish(**request)
Amazon EventBridge(CloudWatch Events)の作成
Amazon EventBridgeでLambda起動のトリガーとルールを作成します。
上記で作成した関数の設定タブにあるデザイナー内のトリガーを追加
を選択し、以下の内容で設定します。
今回チェックは毎日0時としましたが、適切と考える頻度に変えてください。
- EventBridge(CloudWatch Events) を選択
- ルール:新規ルールの作成 を選択
- ルール名:CheckEC2Instances を入力(入力は必須ですが、名前は自由に決めてください)
- ルールの説明:毎日0時にチェック を入力(頻度の内容にあわせて設定してください)
- ルールのタイプ:スケジュール式 を選択
- スケジュール式:
cron(0 15 * * ? *)
を入力 - トリガーの有効化:チェック
- 追加 に進む
cronの書式に関しては、公式ページのRate または Cron を使用したスケジュール式を参考にしてください。エラーがでなければ、関数の設定タブでトリガーが追加されてることが確認できます。これで一連の設定は完了です。
動作確認
メールが届くかの確認自体は、Lambdaのテストイベントを実行すればすぐに確認可能です。常時起動のタグをつけたEC2インスタンスとつけていないEC2インスタンスを起動して意図した通知になるか確認します。
最後に
いかがでしたでしょうか。この仕組みを活用すれば、予算管理者は無駄なコストを気にして能動的にチェックする必要がないため、予算管理者の精神的負担も少しは軽くなる気がします。コストの観点ではデータベースのサービスも大きな割合を占めることが多いので、RDSにも適用できるよう発展すればさらなる効果が期待できますね。
以上です。